package fr.cephb.operonj.tools.cmdline.remapper;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;

import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamWriter;

import org.xml.sax.SAXException;

import com.sleepycat.bind.tuple.StringBinding;
import com.sleepycat.je.Cursor;
import com.sleepycat.je.DatabaseConfig;
import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.LockMode;
import com.sleepycat.je.OperationStatus;
import com.sleepycat.je.Transaction;


import fr.cephb.operonj.core.bio.ChromPosition;
import fr.cephb.operonj.core.bio.Observed;
import fr.cephb.operonj.core.db.database.GenomicSeqDB;
import fr.cephb.operonj.core.db.database.SetOfStringsDB;
import fr.cephb.operonj.core.je.DefaultPrimaryDB;
import fr.cephb.operonj.core.je.PrimaryDB;
import fr.cephb.operonj.core.je.Walker;
import fr.cephb.operonj.tools.cmdline.AbstractApplication;
import fr.cephb.operonj.tools.cmdline.remapper.BowtieHit.CandidatePair;

/**
 * ReMapper
 *
 */
public class ReMapper
	extends AbstractApplication
	{
	private final static  Pattern TAB=Pattern.compile("[\t]");
	private final static  Pattern COMMA=Pattern.compile("[,]");
	private String bowtieDBName="db";
	private String program=null;
	private SNPToInfoDB snpToInfo=null;
	
	private File bowtieHome=new File("bowtie-0.10.1");
	private File blastPath=new File("blat");
	private File fastaDir=new File("/home/pierre/tmp/BOWTIE");//TODO
	private int bowtie_pool_size=10000;
	private SetOfStringsDB currentRsList;
	private int flankingLength=60;
	private final int buildSrc=36;
	private final int buildDest=37;
	private boolean keep_only_uniq_match=false;

	
	static interface MappingTask
		{
		public void init(Transaction txn) throws  Exception;
		public void dispose() throws  Exception;
		public void map(Transaction txn) throws  Exception;
		}
	
	private ReMapper()
		{
		super.envCfg.setAllowCreate(true);
		super.envCfg.setReadOnly(false);
		super.envCfg.setTransactional(false);
		}
	
	@Override
	public void open() throws DatabaseException, IOException
		{
		super.open();
		this.snpToInfo= new SNPToInfoDB(getEnvironment(),null, null, null);
		
		DatabaseConfig dbConfig=new DatabaseConfig();
		dbConfig.setAllowCreate(true);
		dbConfig.setReadOnly(false);
		dbConfig.setTransactional(false);
		//dbConfig.setTemporary(true);
		currentRsList=new SetOfStringsDB(getEnvironment(), null, "currentRsList", dbConfig);
		}
	
	@Override
	public void close()
		{
		if(snpToInfo!=null) snpToInfo.safeClose();
		snpToInfo=null;
		if(currentRsList!=null) currentRsList.safeClose();
		currentRsList=null;
		super.close();
		}
	
	@Override
	protected int processArg(String[] args, int optind)
		{
		int n= super.processArg(args, optind);
		if(n!=-1) return n;
		if(args[optind].equals("-p"))
			{
			this.program =args[++optind];
			return optind;
			}
		else if(args[optind].equals("-bowtie-home"))
			{
			this.bowtieHome =new File(args[++optind]);
			return optind;
			}
		else if(args[optind].equals("-bowtie-pool-size"))
			{
			this.bowtie_pool_size =new Integer(args[++optind]);
			return optind;
			}
		else if(args[optind].equals("-flanking-size"))
			{
			this.flankingLength =new Integer(args[++optind]);
			return optind;
			}
		else if(args[optind].equals("-bowtie-db"))
			{
			this.bowtieDBName = args[++optind];
			return optind;
			}
		else if(args[optind].equals("-blat-binary"))
			{
			this.blastPath = new File(args[++optind]);
			return optind;
			}
		else if(args[optind].equals("-uniq"))
			{
			this.keep_only_uniq_match=true;
			return optind;
			}
		else if(args[optind].equals("-fasta-dir"))
			{
			this.fastaDir=new File(args[++optind]);
			return optind;
			}
		return -1;
		}
	
	@Override
	protected void usage(PrintStream out)
		{
		super.usage(out);
		out.println("Options");
		out.println(" -uniq keep only uniq match");
		out.println(" -blat-binary <path> default:"+ blastPath);
		out.println(" -bowtie-home <path> default:"+bowtieHome);
		out.println(" -fasta-dir <dir> default:"+fastaDir);
		out.println(" -bowtie-db <name> default:"+bowtieDBName);
		out.println(" -bowtie-pool-size <int> default:"+bowtie_pool_size);
		out.println(" -flanking-size <int> default:"+flankingLength);
		out.println(" -p <program>");
		out.println("    load-positions");
		out.println("         <files|stdin>");
		out.println("    load-dbsnp");
		out.println("         <files|stdin>");
		out.println("    map-flanking");
		out.println("    blat-flanking");
		out.println("    dump");
		out.println("    resolve-neighbours");
		}
	
	
	
	private void run(String args[])
		{
		int optind = processArgs(args);
		try
			{
			if(this.program==null)
				{
				System.err.println("undefined program.");
				return;
				}
			else if(program.equals("dump"))
				{
				exportAllResults();
				}
			else if(program.equals("load-positions"))
				{
				loadPositions(optind,args);
				}
			else if(program.equals("load-dbsnp"))
				{
				readDbSnpXML(null,optind,args);
				}
			else if(program.equals("map-flanking"))
				{
				if(optind!=args.length)
					{
					System.err.println("Too Many arguments");
					return;
					}
				open();
				MappingTask task=new MappingFlankBowtie();
				mappingLoop(task);
				close();
				}
			else if(program.equals("blat-flanking"))
				{
				if(optind!=args.length)
					{
					System.err.println("Too Many arguments");
					return;
					}
				open();
				MappingTask task=new MappingFlankBlat();
				mappingLoop(task);
				close();
				}
			else if(program.equals("resolve-neighbours"))
				{
				resolveAmbiguities();
				}
			else
				{
				System.err.println("Unknown position");
				return;
				}
			} 
		catch(Exception err)
			{
			err.printStackTrace();
			}
			
		}
	
	private void readDbSnpXML(Transaction txn,int optind,String args[]) throws Exception,SAXException
		{
		if(optind==args.length)
			{
			System.err.println("Files missing");
			return;
			}
		
		open();
		PrimaryDB<String, String> rs2snp = openRsToSnp(txn);
		while(optind<args.length)
			{
			String fname=args[optind++];
			InputStream in=openStream(fname);
			LOG.info("Start reading XML "+fname);
			SAXParserFactory factory= SAXParserFactory.newInstance();
			factory.setNamespaceAware(true);
			SAXParser parser;
			try
				{
				parser=factory.newSAXParser();
				parser.parse(in, new RsSaxHandler(txn,rs2snp,snpToInfo));
				LOG.info("Count(SNP) "+this.snpToInfo.count());
				}
			catch(Exception err)
				{
				throw new SAXException(err);
				}
			in.close();
			LOG.info("End reading XML");
			}
		rs2snp.close();
		close();	
		}
	
	private static int chromName(String s)
		{
		s=s.trim().toLowerCase();
		if(s.startsWith("chrom")) s=s.substring(5);
		else if(s.startsWith("chr")) s=s.substring(3);
		else if(s.startsWith("k")) s=s.substring(1);
		if(s.equals("x")) return 23;
		if(s.equals("y")) return 24;
		if(s.equals("m")) return 25;
		return Integer.parseInt(s);
		}
	
	
	private GenomicSeqDB openGenomicSeqDB(Transaction txn) throws DatabaseException
		{
		DatabaseConfig dbConfig=new DatabaseConfig();
		dbConfig.setAllowCreate(false);
		dbConfig.setReadOnly(true);
		dbConfig.setTransactional(false);
		return new GenomicSeqDB(getEnvironment(), txn, null, dbConfig);
		}
	
	private PositionToSnpDB openPositionToSnpDB(Transaction txn,int buildid) throws DatabaseException
		{
		DatabaseConfig dbConfig=new DatabaseConfig();
		dbConfig.setAllowCreate(true);
		dbConfig.setReadOnly(false);
		dbConfig.setTransactional(false);
		return new PositionToSnpDB(getEnvironment(),txn,buildid,dbConfig);
		}
	
	private PrimaryDB<String, String> openRsToSnp(Transaction txn) throws DatabaseException
		{
		DatabaseConfig dbConfig=new DatabaseConfig();
		dbConfig.setAllowCreate(true);
		dbConfig.setReadOnly(false);
		dbConfig.setTransactional(false);
		return new DefaultPrimaryDB<String, String>(getEnvironment(),txn,"rs2snp",dbConfig,new StringBinding(),new StringBinding());
		}
	
	
	private void loadPositions(Transaction txn,InputStream input)
		throws Exception
		{
		int nCount=0;
		String line;
		String token[];
		
		GenomicSeqDB genomicSeqDB=openGenomicSeqDB(txn);
		PositionToSnpDB positionToSnp = openPositionToSnpDB(txn,this.buildSrc);
		PrimaryDB<String, String> rs2snp = openRsToSnp(txn);
		
		BufferedReader in= new BufferedReader(new InputStreamReader(input));
		while((line=in.readLine())!=null)
			{
			if(line.startsWith("#")) continue;
			if(line.startsWith("name\t")) continue;
			token=TAB.split(line);
			if(token.length!=6) throw new IOException("Illegal input "+line);
			
			///if(!token[0].equals("rs3927847")) continue;
			
			
			SNPInfo info= this.snpToInfo.get(txn, token[0]);
			SNPInfo.SequenceInput seq=new SNPInfo.SequenceInput();
			seq.chromId= chromName(token[1]);
			seq.start= Integer.parseInt(token[2]);
			seq.end= Integer.parseInt(token[3]);
			
			if(seq.end-seq.start>100)
				{
				LOG.warning("Ignoring "+line+" sequence too large "+(seq.end-seq.start)+" pb");
				continue;
				}
			
			seq.strand= (byte)token[4].charAt(0);
			seq.buildId= this.buildSrc;
			seq.bound_size= 120;
			
			if(info==null)
				{
				//not in database create it
				info=new SNPInfo();
				info.name=token[0];
				try
					{
					if(token[5].equals("lengthTooLong"))
						{
						token[5]="-/"+Observed.LARGE_DELETION_FLAG;
						}
					info.observed=new Observed(token[5]);
					//if(seq.strand=='-') info.observed= info.observed.reverseComplement();
					}
				catch(IllegalArgumentException err)
					{
					throw new IllegalArgumentException("Bad observation "+line);
					}
				
				if(token[0].startsWith("rs"))
					{
					info.rsId=info.name;
					}
				info.positions36.add(seq);
				}
			else
				{
				//duplicate SNP
				if(!info.observed.equals(new Observed(token[5])))
					{
					throw new IOException(token[0]+" "+info.observed+" "+token[5]);
					}
				//add sequence/position if needed
				if(info.positions36.contains(seq))
					{
					LOG.info("duplicate Sequence for "+token[0]+":"+seq);
					}
				else
					{
					info.positions36.add(seq);
					}
				}
			
			
			this.snpToInfo.put(txn, info.name, info);
			
			
			//add this snp at the given position
			for(SNPInfo.SequenceInput seq36: info.positions36)
				{
				positionToSnp.add(txn,
					seq36.chromId,
					seq36.start,
					seq36.end,
					info.name
					);
				}
			
			//if this is a RS name, add it to this position
			if(info.rsId!=null && info.rsId.startsWith("rs"))
				{
				
				if( rs2snp.put(txn, info.rsId,info.name)!=OperationStatus.SUCCESS)
					{
					throw new IOException("Cannot insert");
					}
				assert(rs2snp.get(txn,info.rsId).equals(info.name));
				//LOG.info("inserted "+info.name+" to "+info.rsId);
				}
			++nCount;
			if(nCount==1000) break;//TODO remove this
			}
		rs2snp.close();
		genomicSeqDB.close();
		positionToSnp.close();
		LOG.info("count src position "+this.snpToInfo.count());
		}
	
	
	
	
	
	private void loadPositions(int optind,String args[])
		throws Exception
		{
		Transaction txn=null;
		try
			{
			open();
			if(optind==args.length)
				{
				LOG.info("reading snp from stdin");
				loadPositions(txn,System.in);
				}
			else
				{
				while(optind< args.length)
					{
					String fname=args[optind++];
					LOG.info("reading "+fname);
					InputStream in=openStream(fname);
					loadPositions(txn,in);
					in.close();
					}
				}
			}
		catch(Exception err)
			{
			err.printStackTrace();
			throw err;
			}
		finally
			{
			close();
			}
		}
	
	class MappingFlankBowtie implements MappingTask
		{
		
		@Override
		public void init(Transaction txn) throws Exception
			{
			}
		@Override
		public void dispose() throws Exception
			{
			
			}
		
		public void map(Transaction txn) throws Exception
			{
			assert(ReMapper.this.flankingLength>0);
			LOG.info("mapping "+currentRsList.count()+" snps");
			if(currentRsList.count()==0L) return;
			GenomicSeqDB seqdb= openGenomicSeqDB(txn);
			File tmpFile=File.createTempFile("_bowtie_", ".fa");
			PrintWriter out= new PrintWriter(tmpFile);
			DatabaseEntry key= new DatabaseEntry();
			DatabaseEntry data= new DatabaseEntry();
			Cursor c= ReMapper.this.currentRsList.openCursor(txn);
			while(c.getNext(key, data, LockMode.DEFAULT)==OperationStatus.SUCCESS)
				{
				String name= ReMapper.this.currentRsList.getKeyBinding().entryToObject(key);
				SNPInfo info= ReMapper.this.snpToInfo.get(txn, name);
				
				if(info==null) throw new RuntimeException();
				if(info.positions36.size()!=1)
					{
					//TODO LOG.info("for "+info.name+" TODO");
					continue;
					}
				
				
				for(int i=0;i < info.positions36.size();++i)
					{
					SNPInfo.SequenceInput seq= info.positions36.get(i);
					
					//LOG.info(info.name+" "+seq5);
					out.println(">L_"+info.name);
					out.println(seq.getSeq5(txn, seqdb,ReMapper.this.flankingLength));
					out.println(">R_"+info.name);
					out.println(seq.getSeq3(txn, seqdb,ReMapper.this.flankingLength));
					
					}
				}
			c.close();
			
			out.flush();
			out.close();
			
			
			
			DatabaseConfig cfg= new DatabaseConfig();
			cfg.setAllowCreate(true);
			cfg.setReadOnly(false);
			BowtieHit.HitDB hitsDB = new BowtieHit.HitDB(getEnvironment(),txn,"hits",cfg);
			
			PositionToSnpDB positionToSnpBuild37 = openPositionToSnpDB(txn,ReMapper.this.buildDest);
			GenomicSeqDB genomicSeqDB=openGenomicSeqDB(txn);
			
			//parse the results
			parseBowtieResults(txn,tmpFile,hitsDB);
			
			key= new DatabaseEntry();
			data= new DatabaseEntry();
			c=ReMapper.this.currentRsList.openCursor(txn);
			while(c.getNext(key, data, LockMode.DEFAULT)==OperationStatus.SUCCESS)
				{
				String name= ReMapper.this.currentRsList.getKeyBinding().entryToObject(key);
				SNPInfo info= ReMapper.this.snpToInfo.get(txn, name);
				List<BowtieHit.CandidatePair> candidates= new ArrayList<CandidatePair>();
				
				List<BowtieHit> left= hitsDB.get(txn, "L_"+info.name);
				List<BowtieHit> right= hitsDB.get(txn, "R_"+info.name);
				for(int i=0;i< left.size();++i)
					{
					BowtieHit L= left.get(i);
					for(int j=0;j< right.size();++j)
						{
						final int max_len_deletion=10;
						BowtieHit R= right.get(j);
						if(R.orientation!=L.orientation) continue;
						
						if(R.orientation=='+')
							{
							if(R.position < L.position+ReMapper.this.flankingLength) continue;
							if(R.position - (L.position+ReMapper.this.flankingLength) > max_len_deletion) continue;
							BowtieHit.CandidatePair candidate= new BowtieHit.CandidatePair(L,R,ReMapper.this.flankingLength);
							if(!candidates.isEmpty())
								{
								int cmp= candidates.get(0).compareTo( candidate) ;
								if(cmp > 0)
									{
									candidates.clear();
									}
								else if(cmp<0)
									{
									continue;
									}
								}
							candidates.add(candidate);
							Collections.sort(candidates);
							}
						else if(R.orientation=='-')
							{
							if(L.position < R.position+ReMapper.this.flankingLength) continue;
							if(L.position - (R.position+ReMapper.this.flankingLength) > max_len_deletion) continue;
							BowtieHit.CandidatePair candidate= new BowtieHit.CandidatePair(L,R,ReMapper.this.flankingLength);
							if(!candidates.isEmpty())
								{
								int cmp= candidates.get(0).compareTo( candidate) ;
								if(cmp > 0)
									{
									candidates.clear();
									}
								else if(cmp<0)
									{
									continue;
									}
								}
							candidates.add(candidate);
							Collections.sort(candidates);
							}
						}
					
					}
				
				
			
				if(candidates.size()!=1 &&
						ReMapper.this.keep_only_uniq_match
					)
					{
					LOG.info("multiple hits for "+name);
					continue;
					}
				info.leftHitCount= left.size();
				info.rightHitCount= right.size();
				
				for(int i=0;i< candidates.size();++i)
					{
					BowtieHit.CandidatePair candidate= candidates.get(i);
					
					
					//save position to snp
					positionToSnpBuild37.add(txn,
							candidate.getChromId(),
							candidate.getSNPStart(),
							candidate.getSNPEnd(),
							name
							);
					
					SNPInfo.SequenceOutput seq= new SNPInfo.SequenceOutput();
					seq.chromId=candidate.getChromId();
					seq.strand=candidate.getStrand();
					seq.start=candidate.getSNPStart();
					seq.end=candidate.getSNPEnd();
					
					seq.comment="Mapped with Bowtie. (flanking:"+flankingLength+"pb)";
					seq.buildId=buildDest;
					seq.bound_size= flankingLength;
					
					info.positions37.add(seq);
					}
				 ReMapper.this.snpToInfo.put(txn, name,info);
				
				 //apres, il faut sauver leftHitCount
				 if(candidates.isEmpty())
						{
						LOG.info("No candidate for "+name);
						}
				 
				}
			c.close();
			
			genomicSeqDB.close();
			positionToSnpBuild37.close();
			
			hitsDB.clear(txn);
			hitsDB.close();
			seqdb.close();
			LOG.info("end mapping ");
			}
	}
	
	
	class MappingFlankBlat implements MappingTask
	{
	
	@Override
	public void init(Transaction txn) throws Exception
		{
		}
	@Override
	public void dispose() throws Exception
		{
		
		}
	
	public void map(Transaction txn) throws Exception
		{
		assert(ReMapper.this.flankingLength>0);
		LOG.info("mapping "+currentRsList.count()+" snps");
		if(currentRsList.count()==0L) return;
		GenomicSeqDB seqdb= openGenomicSeqDB(txn);
		File tmpFile=File.createTempFile("_blat_", ".fa");
		PrintWriter out= new PrintWriter(tmpFile);
		DatabaseEntry key= new DatabaseEntry();
		DatabaseEntry data= new DatabaseEntry();
		Cursor c= ReMapper.this.currentRsList.openCursor(txn);
		while(c.getNext(key, data, LockMode.DEFAULT)==OperationStatus.SUCCESS)
			{
			String name= ReMapper.this.currentRsList.getKeyBinding().entryToObject(key);
			SNPInfo info= ReMapper.this.snpToInfo.get(txn, name);
			
			if(info==null) throw new RuntimeException();
			if(info.positions36.size()!=1)
				{
				//TODO LOG.info("for "+info.name+" TODO");
				continue;
				}
			
			
			for(int i=0;i < info.positions36.size();++i)
				{
				SNPInfo.SequenceInput seq= info.positions36.get(i);
				
				//LOG.info(info.name+" "+seq5);
				out.println(">L_"+info.name);
				out.println(seq.getSeq5(txn, seqdb,ReMapper.this.flankingLength));
				out.println(">R_"+info.name);
				out.println(seq.getSeq3(txn, seqdb,ReMapper.this.flankingLength));
				
				}
			}
		c.close();
		
		out.flush();
		out.close();
		
		
		
		DatabaseConfig cfg= new DatabaseConfig();
		cfg.setAllowCreate(true);
		cfg.setReadOnly(false);
		BlatHit.HitDB hitsDB = new BlatHit.HitDB(getEnvironment(),txn,cfg);
		
		PositionToSnpDB positionToSnpBuild37 = openPositionToSnpDB(txn,ReMapper.this.buildDest);
		GenomicSeqDB genomicSeqDB=openGenomicSeqDB(txn);
		
		//parse the results
		callBlat(txn,tmpFile,hitsDB);
		
		key= new DatabaseEntry();
		data= new DatabaseEntry();
		c=ReMapper.this.currentRsList.openCursor(txn);
		while(c.getNext(key, data, LockMode.DEFAULT)==OperationStatus.SUCCESS)
			{
			String name= ReMapper.this.currentRsList.getKeyBinding().entryToObject(key);
			SNPInfo info= ReMapper.this.snpToInfo.get(txn, name);
			List<BlatHit.CandidatePair> candidates= new ArrayList<BlatHit.CandidatePair>();
			
			List<BlatHit> left= hitsDB.get(txn, "L_"+info.name);
			List<BlatHit> right= hitsDB.get(txn, "R_"+info.name);
			System.err.println(name);
			//System.err.println("L:"+left);
			//System.err.println("R:"+right);
			for(int i=0;i< left.size();++i)
				{
				BlatHit L= left.get(i);
				for(int j=0;j< right.size();++j)
					{
					final int max_len_deletion=10;
					BlatHit R= right.get(j);
					if(R.strand!=L.strand) continue;
					
					if(R.strand=='+')
						{
						if(R.t_start < L.t_end)
							{
							continue;
							}
						if(R.t_start - L.t_end > max_len_deletion)
							{
							continue;
							}
						
						BlatHit.CandidatePair candidate= new BlatHit.CandidatePair(L,R);
						if(!candidates.isEmpty())
							{
							int compare= candidates.get(0).compareTo(candidate);
							if(compare>0)
								{
								candidates.clear();
								}
							else if(compare<0)
								{
								continue;
								}
							}
						candidates.add(candidate);
						Collections.sort(candidates);
						}
					else if(R.strand=='-')
						{
						if(R.t_end > L.t_start)
							{
							continue;
							}
						if(L.t_start - R.t_end > max_len_deletion)
							{
							continue;
							}
					
						BlatHit.CandidatePair candidate= new BlatHit.CandidatePair(L,R);
						if(!candidates.isEmpty())
							{
							int compare= candidates.get(0).compareTo(candidate);
							if(compare>0)
								{
								candidates.clear();
								}
							else if(compare<0)
								{
								continue;
								}
							}
						candidates.add(candidate);
						Collections.sort(candidates);
						}
					}
				
				}
			
			
			
			if(candidates.size()!=1 &&
					ReMapper.this.keep_only_uniq_match
				)
				{
				LOG.info("multiple hits for "+name);
				continue;
				}
			
			info.leftHitCount= left.size();
			info.rightHitCount= right.size();
			//save each position
			for(int i=0;i< candidates.size();++i)
				{
				BlatHit.CandidatePair candidate= candidates.get(i);
				
				//save position to snp
				positionToSnpBuild37.add(txn,
						candidate.getChromId(),
						candidate.getSNPStart(),
						candidate.getSNPEnd(),
						name
						);
				
				SNPInfo.SequenceOutput seq= new SNPInfo.SequenceOutput();
				seq.chromId=candidate.getChromId();
				seq.strand=candidate.getStrand();
				seq.start=candidate.getSNPStart();
				seq.end=candidate.getSNPEnd();
				
				seq.comment="Mapped with BLAT. Right:"+candidate.R+" Left:"+candidate.L;
				seq.buildId= buildDest;
				seq.bound_size=flankingLength;
				
				
				info.positions37.add(seq);
				}
			 ReMapper.this.snpToInfo.put(txn, name,info);
			 
			 //apres, il faut sauver leftHitCount
			 if(candidates.isEmpty())
					{
					LOG.info("No candidate for "+name);
					}
			}
		c.close();
		
		genomicSeqDB.close();
		positionToSnpBuild37.close();
		hitsDB.clear(txn);
		hitsDB.close();
		seqdb.close();
		LOG.info("end mapping ");
		}
	}
	
	
	private void mappingLoop(MappingTask task) throws Exception
		{
		LOG.warning("Entering LOOP "+task.getClass());
		Transaction txn=null;
		int count=0;
		assert(bowtie_pool_size>0);
		DatabaseEntry key= new DatabaseEntry();
		DatabaseEntry value= new DatabaseEntry();
		Cursor c=null;
		task.init(txn);
		try
			{
			this.currentRsList.clear(txn);
			c=this.snpToInfo.openCursor(txn);
			OperationStatus status;
			while(true)
				{
				status=c.getNext(key, value, LockMode.DEFAULT);
				
				if(count==this.bowtie_pool_size ||
					status!=OperationStatus.SUCCESS)
					{
					if(count>0)
						{
						assert(this.currentRsList.count()>0);
						LOG.warning("Calling task");
						task.map(txn);
						count=0;
						this.currentRsList.clear(txn);
						}
					if(status!=OperationStatus.SUCCESS)
						{
						break;
						}
					}
				
				String name= this.snpToInfo.entryToKey(key);
				SNPInfo info= this.snpToInfo.entryToValue(value);
				assert(name.equals(info.name));
				assert(name.length()>0);
				if(info.isMapped())
					{
					LOG.info(name+" already mapped");
					continue;
					}
				
				if(!currentRsList.add(txn,name))
					{
					LOG.info("Cannot insert "+name);
					}
				else
					{
					++count;
					}
				}
			LOG.warning("Exiting LOOP");
			}
		catch(Exception err)
			{
			throw err;
			}
		finally
			{
			task.dispose();
			if(c!=null) c.close();
			}
		}
	
	
	
	private void callBlat(Transaction txn,File readFile,BlatHit.HitDB database)
	throws DatabaseException,IOException,InterruptedException
		{
		 final File outFile=new File("/tmp/_blat.out");
		 Runtime runtime=Runtime.getRuntime();
		 //File chromDir=new File("/home/pierre/tmp/BOWTIE");//TODO
		
		 for(File fastaFile: this.fastaDir.listFiles(new FilenameFilter()
	     	{
	     	@Override
	     	public boolean accept(File dir, String name)
	     		{
	     		return name.endsWith(".fa");
	     		}
	     	}))
	    	 {
	    	 String cmdarray[]={
	    			this.blastPath.toString(),
	    			"-t=dna","-q=dna","-noHead",
	    			fastaFile.toString(),
	                readFile.toString(),
	                outFile.toString()
	                };
	        
		 	LOG.info("sending blat to "+fastaFile);
		 	Process proc=runtime.exec(cmdarray);
		 	int returnValue= proc.waitFor();
	        if(returnValue!=0)
	                {
	                System.err.print("Arguments :\n");
	                for(String s:cmdarray) System.err.print(s+" ");
	                System.err.println();
	                ///throw new IOException("blat returned value!=0 : "+returnValue);
	                }
	        LOG.info("reading result "+fastaFile);
	        
	        BufferedReader in=new BufferedReader(new FileReader(outFile));
	        String line;
	        String token[];
	     
	        while((line=in.readLine())!=null)
	            {
	            if(line.trim().length()==0) continue;
	            if(!Character.isDigit(line.charAt(0))) continue;
	            token= TAB.split(line);
	            token[13]=String.valueOf(chromName(token[13]));
	            BlatHit hit= new BlatHit(token);
	            database.put(txn, hit.q_name, hit);
	            }
	        
	    	}
	    
		}
	
	
	private void parseBowtieResults(
			Transaction txn,
			File readFile,
			BowtieHit.HitDB database
			) throws DatabaseException,IOException,InterruptedException
        {
        //final File outFile=File.createTempFile("_bowtie", ".out");
        final File outFile=new File("/tmp/_bowtie.out");
        Runtime runtime=Runtime.getRuntime();
        String cmdarray[]={
                this.bowtieHome.toString()+"/bowtie",
                "-f",
                "-a",//report all alignments
                "-v","3",
                //"--un","/tmp/_unaligned.txt",//write unalignedreads/pairs to file(s)"
                bowtieDBName,
                readFile.toString(),
                outFile.toString()
                };
        
        LOG.info("sending bowtie ");
        Process proc=runtime.exec(cmdarray);
        if(proc.waitFor()!=0)
                {
                for(String s:cmdarray) System.err.print(s+" ");
                throw new IOException("bowtie returned value!=0");
                }
        LOG.info("reading result ");
        //read results
        BufferedReader in=new BufferedReader(new FileReader(outFile));
        String line;
        String token[];
     
        while((line=in.readLine())!=null)
            {
            token= TAB.split(line);
            BowtieHit hit= new BowtieHit();
            assert(token.length>=7);
            assert(token[1].equals("+") || token[1].equals("-"));
            hit.readName= token[0];
            //SNPInfo info = this.snpToInfo.get(txn,  hit.readName);
            hit.orientation=(byte)token[1].charAt(0);
            hit.chromId=chromName(token[2]);
            hit.position=Integer.parseInt(token[3]);
            if(token.length>7 && token[7].length()>0)
                {
                for(String mism: COMMA.split(token[7]))
                    {
                    int dot= mism.indexOf(':');
                    assert(dot!=-1);
                    int lt= mism.indexOf('>',dot+1);
                    assert(lt>dot);
                    assert(lt+2==mism.length());
                    BowtieHit.Mismatch m= new  BowtieHit.Mismatch();
                    m.position= Integer.parseInt(mism.substring(0,dot));
                    //if( m.position == Math.min(info.seq5.length(),boundSize)) continue;//ignore the SNP
                    m.query=(byte)(mism.charAt(dot+1));
                    m.object=(byte)(mism.charAt(lt+1));
                    hit.mismatches.add(m);
                    }
               }
         if(database.put(txn, hit.readName, hit)!=OperationStatus.SUCCESS)
        	 {
        	 LOG.info("Cannot put "+hit);
        	 }
         }
    in.close();
    LOG.info("end bowtie n="+database.count());
    }


	
private void resolveAmbiguities()throws Exception
	{
	Transaction txn=null;
	open();
	
	
	PositionToSnpDB pos2snp = openPositionToSnpDB(txn, buildSrc);
	Walker<ChromPosition, Set<String>> c2=pos2snp.openWalker(txn);
	Walker<String, SNPInfo> c= this.snpToInfo.openWalker(txn);
	while(c.getNext()==OperationStatus.SUCCESS)
		{
		String name= c.getKey();
		SNPInfo snpInfo= c.getValue();
		SNPInfo left=null;
		SNPInfo right=null;
		if(!(snpInfo.positions37.size()>1 &&
		    snpInfo.positions36.size()==1)) continue;
		
		SNPInfo.SequenceInput srcpos=  snpInfo.positions36.get(0);
		

		if(c2.getSearchKeyRange(new ChromPosition(srcpos.chromId, srcpos.start))!=OperationStatus.SUCCESS)
			{
			continue;
			}
		//assert(pos2snp.entryToValue(data).contains(name));
		while(c2.getPrev()==OperationStatus.SUCCESS)
			{
			ChromPosition pos= c2.getKey();
			//System.err.println("Going back:"+pos+" < "+srcpos.chromId+":"+srcpos.start);
			if(pos.getChromId()!=srcpos.chromId) break;
			assert(pos.getPosition()< srcpos.start);
			Set<String> snpsAtThisPosition= c2.getValue();
			for(String s:snpsAtThisPosition)
				{
				if(s.equals(name)) continue;
				SNPInfo other= snpToInfo.get(txn, s);
				if(other.positions36.size()!=1) continue;
				if(other.positions37.size()!=1) continue;
				if(other.positions36.get(0).chromId!=other.positions37.get(0).chromId)
					{
					continue;
					}
				left=other;
				break;
				}
			if(left!=null) break;
			}
		if(left==null)
			{
			continue;
			}
		
	
		if(c2.getSearchKeyRange(new ChromPosition(srcpos.chromId, srcpos.start))!=OperationStatus.SUCCESS)
			{
			continue;
			}
		//assert(pos2snp.entryToValue(data).contains(name));
		while(c2.getNext()==OperationStatus.SUCCESS)
			{
			ChromPosition pos= c2.getKey();
			if(pos.getChromId()!=srcpos.chromId) break;
			if(pos.getPosition()< srcpos.end) break;
			assert(pos.getPosition()>= srcpos.start);
			for(String s: c2.getValue())
				{
				if(s.equals(name)) continue;
				SNPInfo other= snpToInfo.get(txn, s);
				if(other.positions36.size()!=1) continue;
				if(other.positions37.size()!=1) continue;
				if(other.positions36.get(0).chromId!=other.positions37.get(0).chromId)
					{
					continue;
					}
				right=other;
				break;
				}
			if(right!=null) break;
			}
		if(right==null)
			{
			continue;
			}
		
		
		LOG.info("SNP "+name+" k:"+srcpos.chromId+":"+srcpos.start+
				" can be saved with "+left.name+" "+right.name);
		SNPInfo.SequenceOutput leftDest= left.positions37.get(0);
		SNPInfo.SequenceOutput rightDest= right.positions37.get(0);
		int indexSeqBetween=-1;
		for(int i=0;i< snpInfo.positions37.size();++i)
			{
			SNPInfo.SequenceOutput srcdest= snpInfo.positions37.get(i);
			if(srcdest.chromId!=leftDest.chromId) continue;
			if(leftDest.start > srcdest.end) continue;
			if(srcdest.start > rightDest.end) continue;
			if(indexSeqBetween!=-1)
				{
				LOG.info("Ambiguity");
				indexSeqBetween=-1;
				break;
				}
			else
				{
				indexSeqBetween=i;
				}
			}
		if(indexSeqBetween==-1)
			{
			LOG.info("Cannot save this snp");
			continue;
			}
		LOG.info("SNP saved");
		
		SNPInfo.SequenceOutput srcdest= snpInfo.positions37.get(indexSeqBetween);
		srcdest.comment+=" This snp was resolved because it is known to be between "+left.name+" and "+right.name ;
		snpInfo.positions37.clear();
		snpInfo.positions37.add(srcdest);
		
		//update snpInfo
		this.snpToInfo.put(txn, name, snpInfo);
		}
	c2.close();
	c.close();
	pos2snp.close();
	close();
	}
	
	
/** export all results */
private void exportAllResults()
	throws Exception
	{
	open();
	Transaction txn=null;
	GenomicSeqDB seqdb= openGenomicSeqDB(txn);
	DatabaseEntry key= new DatabaseEntry();
	DatabaseEntry value= new DatabaseEntry();
	
	XMLOutputFactory factory= XMLOutputFactory.newInstance();
	//factory.setProperty(XMLOutputFactory., value);
	XMLStreamWriter w=factory.createXMLStreamWriter(System.out);
	
	Cursor c= this.snpToInfo.openCursor(null);
	w.writeStartDocument("UTF-8", "1.0");
	w.writeStartElement("re-mapping");
	
	while(c.getNext(key, value, LockMode.DEFAULT)==OperationStatus.SUCCESS)
		{
		
		String name= this.snpToInfo.entryToKey(key);
		SNPInfo snpInfo= this.snpToInfo.entryToValue(value);
		
		w.writeCharacters("\n");
		
		w.writeStartElement("snp");
		w.writeAttribute("name",name);
		if(snpInfo.rsId.length()>0)
			{
			w.writeAttribute("rs-id",snpInfo.rsId);
			}
		
		
		
		w.writeStartElement("observed");
		w.writeCharacters(snpInfo.observed.asString());
		w.writeEndElement();
		
		if(snpInfo.ncbiSeq5.length()>0)
			{
			w.writeStartElement("ncbi");
			
				w.writeCharacters("\n");
				w.writeStartElement("seq5");
				w.writeCharacters(snpInfo.ncbiSeq5);
				w.writeEndElement();
				w.writeCharacters("\n");
				
				w.writeStartElement("obs");
				w.writeCharacters(snpInfo.ncbiObserved.asString());
				w.writeEndElement();
				w.writeCharacters("\n");
				
				w.writeStartElement("seq3");
				w.writeCharacters(snpInfo.ncbiSeq3);
				w.writeEndElement();
				w.writeCharacters("\n");
				
				if(!snpInfo.ncbiHandles.isEmpty())
					{
					w.writeStartElement("handles");
					for(String s:snpInfo.ncbiHandles)
						{
						w.writeStartElement("handle");
						w.writeCharacters(s);
						w.writeEndElement();
						}
					w.writeEndElement();
					}
				
				if(!snpInfo.ncbiObserved.equals(snpInfo.observed))
					{
					w.writeStartElement("warning");
					w.writeCharacters("Definition "+snpInfo.observed+" is not the same as dbSNP:"+snpInfo.ncbiObserved);
					w.writeEndElement();
					w.writeCharacters("\n");
					}
				
			w.writeEndElement();
			}
		
		w.writeStartElement("source-mapping");
		w.writeAttribute("count",String.valueOf(snpInfo.positions36.size()));
		for(SNPInfo.SequenceInput seq:snpInfo.positions36)
			{
			seq.write(txn,seqdb,w);
			
			Observed obs= snpInfo.observed;
			//if(seq.strand==(byte)'-') obs=obs.reverseComplement();
			String seq_observed = seq.getObserved(txn, seqdb);
			if(!obs.isCompatibleWith(seq_observed))
				{
				w.writeStartElement("warning");
				w.writeCharacters("Definition "+obs+" is incompatible with "+seq_observed);
				w.writeEndElement();
				w.writeCharacters("\n");
				}
			}
		w.writeEndElement();
		
		w.writeStartElement("dest-mapping");
		w.writeAttribute("count",String.valueOf(snpInfo.positions37.size()));
		w.writeAttribute("leftHitCount",String.valueOf(snpInfo.leftHitCount));
		w.writeAttribute("rightHitCount",String.valueOf(snpInfo.rightHitCount));
		for(SNPInfo.SequenceOutput seq:snpInfo.positions37)
			{
			seq.write(txn,seqdb,w);
			
			Observed obs= snpInfo.observed;
			//if(seq.strand==(byte)'-') obs=obs.reverseComplement();
			String seq_observed = seq.getObserved(txn, seqdb);
			if(!obs.isCompatibleWith(seq_observed))
				{
				w.writeStartElement("warning");
				w.writeCharacters("Definition "+obs+" is incompatible with "+seq_observed);
				w.writeEndElement();
				w.writeCharacters("\n");
				}
			}
		
		w.writeEndElement();
		
		w.writeEndElement();
		w.writeCharacters("\n");
		}
	w.writeEndElement();
	w.writeEndDocument();
	w.flush();
	w.close();
	c.close();
	seqdb.close();
	close();
	}

public static void main(String[] args)
	{
	try
		{
		ReMapper app= new ReMapper();
		app.run(args);
		}
	catch (Exception err)
		{
		err.printStackTrace();
		}
	}
}
